#include "herm/base/utils/misc.h"
#include "socket_def.h"
#include  "address.h"
#include "stream.h"

namespace herm{
Connector::Connector(void)
{
}

Connector::~Connector(void)
{
}

void Connector::Connect(const Address& remoteAddr, Stream& s, bool isReuseAddr)
{
	s.Open(isReuseAddr);
	DoConnect(remoteAddr, s);
}

void Connector::Connect(const Address& localAddr, const Address& remoteAddr, Stream& s, bool isReuseAddr)
{
	s.Open(localAddr, isReuseAddr);
	DoConnect(remoteAddr, s);
}

void Connector::DoConnect(const Address& remoteAddr, Stream& s)
{
	if (::connect(s.GetHandle(), (sockaddr*)&remoteAddr.SockAddr(),
		sizeof(sockaddr)) == Private::FAILED)
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);
}

void Connector::ConnectNB(const Address& remoteAddr, Stream& s, bool isReuseAddr)
{
	s.Open(isReuseAddr);
	DoConnectNB(remoteAddr, s);
}

void Connector::ConnectNB(const Address& localAddr, const Address& remoteAddr, Stream& s, bool isReuseAddr)
{
	s.Open(localAddr, isReuseAddr);
	DoConnectNB(remoteAddr, s);
}

void Connector::DoConnectNB(const Address& remoteAddr, Stream& s)
{
	s.SetNonblock();
	int ret = ::connect(s.GetHandle(), (sockaddr*)&remoteAddr.SockAddr(), sizeof(sockaddr));
	if (ret == Private::FAILED && Private::GetLastError() != Private::HEINPROGRESS)
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);
}

Acceptor::Acceptor(const Address& addr, bool isReuseAddr)
{
	Open(addr, isReuseAddr);
	if (listen(GetHandle(), 5) == Private::FAILED)
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);
}

Acceptor::~Acceptor(void)
{
}

void Acceptor::Accept(Stream& s)
{
	HERM_ASSERT (IsBlock());

	Handle sock = ::accept(GetHandle(), nullptr, nullptr);
	if (sock == INVALID_HANDLE)
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);

	s.Attach(sock);
}

void Acceptor::Accept(Stream& s, Address& addr)
{
	HERM_ASSERT (IsBlock());

	sockaddr_in sin;
	memset(&sin, '\0', sizeof(sockaddr_in));
	SockLen len = sizeof(sockaddr_in);
	Handle sock = ::accept(GetHandle(), (sockaddr*)&sin, &len);
	if (sock == INVALID_HANDLE)
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);

	s.Attach(sock);
	addr.SockAddr(sin);
}

int Acceptor::AcceptNB(Stream& s)
{
	HERM_ASSERT (!IsBlock());

	Handle sock = ::accept(GetHandle(), nullptr, nullptr);
	if (sock == INVALID_HANDLE && Private::GetLastError() != Private::HEWOULDBLOCK)
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);

	if (sock == INVALID_HANDLE)
		return COMPLETION;

	s.Attach(sock);
	s.SetNonblock();

	return PENDING;
}

int Acceptor::AcceptNB(Stream& s, Address& addr)
{
	HERM_ASSERT (!IsBlock());

	sockaddr_in sin;
	memset(&sin, '\0', sizeof(sockaddr_in));
	SockLen len = sizeof(sockaddr_in);
	Handle sock = ::accept(GetHandle(), (sockaddr*)&sin, &len);
	if (sock == INVALID_HANDLE && Private::GetLastError() != Private::HEWOULDBLOCK)
		throw HERM_SOCKET_EXCEPTION(ST_OTHER);

	if (sock == INVALID_HANDLE)
		return COMPLETION;

	s.Attach(sock);
	s.SetNonblock();
	addr.SockAddr(sin);

	return PENDING;
}

Stream::Stream(void)
{
}

Stream::~Stream(void)
{
}

void Stream::Send(const char* buffer, int length)
{
	HERM_ASSERT(IsBlock());

	int total = 0;
	for (int bytes = 0; total < length; total += bytes)
	{
		bytes = ::send(GetHandle(), buffer + total, length - total, Private::HOP_FLAG_NOSIGNAL);
		if (bytes == Private::FAILED)
			throw HERM_SOCKET_EXCEPTION(ST_SEND_FAILED);
	}
}

int Stream::Receive(char* buffer, int length)
{
	HERM_ASSERT(IsBlock());

	int bytes = ::recv(GetHandle(), buffer, length, 0);
	if (bytes == Private::FAILED)
		throw HERM_SOCKET_EXCEPTION(ST_RECV_FAILED);
	
	return bytes;
}

int Stream::SendNB(const char* buffer, int length)
{
	HERM_ASSERT(!IsBlock());

	int bytes = ::send(GetHandle(), buffer, length, Private::HOP_FLAG_NOSIGNAL);
	if (bytes == Private::FAILED && Private::GetLastError() != Private::HEWOULDBLOCK)
		throw HERM_SOCKET_EXCEPTION(ST_SEND_FAILED);
	
	return bytes;
}

int Stream::ReceiveNB(char* buffer, int length)
{
	HERM_ASSERT(!IsBlock());

	int bytes = ::recv(GetHandle(), buffer, length, 0);
	if (bytes == Private::FAILED && Private::GetLastError() != Private::HEWOULDBLOCK)
		throw HERM_SOCKET_EXCEPTION(ST_RECV_FAILED);

	return bytes;
}
} // end namespace herm
